#include "stdafx.h"

//#include <atlbase.h>


#include "DMAlgorithm.h"
#include "DMAttributeSet.h"
#include "DMMiningParameter.h"
#include "DMAlgorithmPersistence.h"
#include "DMAlgorithmTrainPredict.h"
#include "DMAlgorithmNavigation.h"
#include "DMMetadataAndFactory.h"
#include "DMUtils.h"
using namespace PluginAlgorithms;
using namespace System::Globalization;

AlgorithmBase::AlgorithmBase()
{
	m_context = gcnew ContextServices();
	m_model = nullptr;
	m_pmmlSwitchDepth	=	0;
}

AlgorithmBase::~AlgorithmBase()
{
	m_context = nullptr;
	m_attrSet = nullptr;
	m_marginalStats = nullptr;
	m_model= nullptr;

	try
	{
		if( m_pmmlStream != nullptr )
		{
			m_pmmlStream->Close();
		}

		if( m_pmmlRecorder!= nullptr )
		{
			m_pmmlRecorder->Close();
		}
	}
	catch(...)
	{
	}

}

/// <summary>
/// ExportedInitialize does nothing
/// </summary>
HRESULT AlgorithmBase::ExportedInitialize(
	IDMContextServices* in_pContext ,
	IDMAttributeSet*	in_pAttributeSet ,
	IDMMarginalStat*	in_pMarginalStat)
{
	AutoPushContext autoCtx(in_pContext);

	USES_ERROR_HANDLING;

	DM_TRY
	{
		m_attrSet = gcnew PluginAlgorithms::AttributeSet( in_pAttributeSet );
		m_marginalStats = gcnew PluginAlgorithms::MarginalStats( in_pMarginalStat, in_pAttributeSet );

	}
	DM_CATCHALL;
	
	REPORT_STATUS;
}

HRESULT AlgorithmBase::ExportedInsertCases( 
    IDMContextServices *in_pExeContext,
    IDMPushCaseSet *in_pCaseSet,
    ULONG in_ulNumParameters,
    WCHAR*  in_rgParameterNames [  ],
    DMVariantPtr  in_rgParameterValues [  ])
{
	AutoPushContext autoCtx(in_pExeContext);
	USES_ERROR_HANDLING;

	VARIANT     varParameter;
	::VariantInit( &varParameter);

	DM_TRY
	{
		// Initialize is called late, to ensure that the marginal statistics and the attribute set
		// are fully populated
		Initialize();


		PushCaseSet ^ caseSet = gcnew PushCaseSet( in_pCaseSet );

		/////////////////////////////////////////////////////////////
		// Convert DMvariantPtr parameters to MiningParameters
		CComPtr<IDMVariantPtrHandler> spVarHandler;
		THROW_ON_FAILURE( in_pExeContext->GetVariantHandler( &spVarHandler ) );
		
		
		PluginAlgorithms::MiningParameterCollection ^ paramColl = gcnew PluginAlgorithms::MiningParameterCollection ();

		for( ULONG ulIndex = 0; ulIndex < in_ulNumParameters; ulIndex ++ )
		{
            ::VariantInit( &varParameter);
            THROW_ON_FAILURE( spVarHandler->CopyHandleToVariant( in_rgParameterValues[ulIndex], &varParameter) );
	
			Object ^	objValue	=	System::Runtime::InteropServices::Marshal::GetObjectForNativeVariant((System::IntPtr)&varParameter);
			String ^	paramName = gcnew String(in_rgParameterNames[ulIndex]);
			
			// Find the parameter in the meetadata collection
			// then cast the actual value to the declared type
			MiningParameterCollection^	params = this->Model->Metadata->GetParametersCollection();
			MiningParameter^ archtype = params[paramName];
			try
			{
				objValue = System::Convert::ChangeType(objValue, archtype->Type, CultureInfo::InvariantCulture);
			}
			catch(System::Exception^)
			{
				throw gcnew System::InvalidCastException(String::Format(CultureInfo::InvariantCulture, " ({0})", paramName));
			}
			
			MiningParameter ^ param = gcnew MiningParameter(paramName, archtype->Type);
			param->SetParameterValue( objValue );

			paramColl->Add( param );

			::VariantClear(&varParameter);
		}
		
		InsertCases( caseSet, paramColl );
	}
	DM_CATCHALL
	DM_FINALLY
	{
		::VariantClear(&varParameter);
	}

	REPORT_STATUS;
}


HRESULT AlgorithmBase::ExportedPredict( 
    IDMContextServices *in_pContext,
    DM_PREDICTION_FLAGS in_Flags,
    IDMAttributeGroup *in_pPredAttGroup,
    DM_CaseID in_CaseID,
    ULONG in_ulCaseValues,
    DM_ATTRIBUTE_VALUE *in_rgValues,
    ULONG in_ulMaxPredictions,
    ULONG in_ulMaxStates,
    DM_ATTRIBUTE_STAT **io_prgPredictions,
    ULONG *out_pulPredictions)
{
	AutoPushContext autoCtx(in_pContext);
	USES_ERROR_HANDLING;



	PredictionResult ^	prediction	 = nullptr;

	DM_TRY
	{
		prediction	=	gcnew PredictionResult(
														in_Flags, 
														in_pPredAttGroup, 
														in_ulMaxPredictions, 
														in_ulMaxStates,
														in_rgValues,
														in_ulCaseValues);

		THROW_ON_FAILURE( prediction->SetOutputBuffers( io_prgPredictions, out_pulPredictions ) );

		MiningCase ^	inputCase	=	gcnew MiningCase();
		DMCaseItr caseiter;

		
		// Initialize the case iterator with the input values
		caseiter.SetCase(in_rgValues, in_ulCaseValues);
		inputCase->SetCaseIterator( &caseiter );

		Predict( inputCase, prediction );

		prediction->PostProcess();
	}
	DM_CATCH_EX(System::Exception^ ex)
	{
		DM_HANDLE_EXCEPTION(ex);
		// An exception was thrown -- cleanup the PredictionResult object
		if( prediction != nullptr)
		{
			prediction->ErrorCleanup();
		}
	}

	REPORT_STATUS;
}


HRESULT AlgorithmBase::ExportedGetNodeIDsForCase( 
    IDMContextServices *in_pContext,
    DM_NODE_FLAGS in_Flags,
    DM_CaseID in_CaseID,
    ULONG in_ulCaseValues,
    DM_ATTRIBUTE_VALUE *in_rgValues,
    ULONG in_ulMaxNodeIDs,
    DMStringPtr **io_prgNodeIDs,
    ULONG *out_pulNodeIDs)
{
	AutoPushContext autoCtx(in_pContext);
	USES_ERROR_HANDLING;


	bool		fOwnsVector = false;
	ULONG		cCount = 0;
	BSTR		bstrTmp = NULL;

	CComPtr<IDMMemoryAllocator>	spAlloc;
	THROW_ON_FAILURE(in_pContext->GetVectorAlloc( &spAlloc ));

	CComPtr<IDMStringHandler>	spStringHandler;
	THROW_ON_FAILURE(in_pContext->GetStringHandler( &spStringHandler));



	DM_TRY
	{
		MiningCase ^	inputCase	=	gcnew MiningCase();
		DMCaseItr caseiter;

		// Node flags
		DrillThroughNodeFlags	nodeFlags = (DrillThroughNodeFlags)in_Flags;

		// Initialize the case iterator with the input values
		caseiter.SetCase(in_rgValues, in_ulCaseValues);
		inputCase->SetCaseIterator( &caseiter );
		
		*io_prgNodeIDs	= NULL;
		*out_pulNodeIDs	= 0;

		// get the node IDs for case
		array<String^>^  rgNodeIDs= nullptr;
		System::Int64 caseID = (in_CaseID == DM_UNSPECIFIED) ? -1 : (System::Int64)in_CaseID;
		rgNodeIDs = GetNodeIDsForCase( nodeFlags, caseID, inputCase, in_ulMaxNodeIDs);

		if( rgNodeIDs->Length > 0 )
		{
			cCount = ((ULONG)rgNodeIDs->Length  > in_ulMaxNodeIDs) ? in_ulMaxNodeIDs : (ULONG)rgNodeIDs->Length;
			*out_pulNodeIDs = cCount;
			// Allocate the buffer if necessary
			if (!*io_prgNodeIDs)
			{
				byte* pBuff = NULL;
				THROW_ON_FAILURE( spAlloc->Alloc( cCount*sizeof(DMStringPtr), &pBuff) );
				*io_prgNodeIDs = (DMStringPtr*)pBuff;
				fOwnsVector = true;
			}
			memset( *io_prgNodeIDs, 0, cCount*sizeof(DMStringPtr) );

			// Copy the strings
			for( ULONG nIndex = 0; nIndex < cCount; nIndex ++ )
			{
				DMStringPtr		stringHandle = NULL;
				String^ currString	=	rgNodeIDs[nIndex];

				THROW_ON_FAILURE( spStringHandler->CreateNewStringHandle( &stringHandle) );
				(*io_prgNodeIDs)[nIndex] = stringHandle;

				bstrTmp = (BSTR)(void*)System::Runtime::InteropServices::Marshal::StringToBSTR( currString );
				THROW_ON_FAILURE( spStringHandler->CopyBufferToHandle( (*io_prgNodeIDs)[nIndex], bstrTmp, currString->Length) );

				System::Runtime::InteropServices::Marshal::FreeBSTR( (IntPtr)(void*)bstrTmp );
				bstrTmp = NULL;
			}
		}
	}
	DM_CATCH_EX(System::Exception^ ex)
	{
		DM_HANDLE_EXCEPTION(ex);
		
		// Cleanup the buffer
		if(  *io_prgNodeIDs != NULL )
		{
			for( ULONG nIndex = 0; nIndex < *out_pulNodeIDs; nIndex ++ )
			{
				DMStringPtr	hString = (*io_prgNodeIDs)[nIndex];
				if( hString != NULL )
				{
					spStringHandler->DestroyStringHandle(hString);
				}
			}

			if( fOwnsVector )
			{
				spAlloc->Free((byte*)(*io_prgNodeIDs));
				*io_prgNodeIDs = NULL;
			}
			*out_pulNodeIDs = 0;
		}
	}
	DM_FINALLY
	{
		if( bstrTmp != NULL )
		{
			System::Runtime::InteropServices::Marshal::FreeBSTR( (IntPtr)(void*)bstrTmp );
		}
	}
	
	REPORT_STATUS;
}


HRESULT AlgorithmBase::ExportedGetNavigator( 
    IDMContextServices *in_pContext,
    BOOL in_fForDMDimensionContent,
    IDMAlgorithmNavigation **out_ppDAGNav)
{
	AutoPushContext autoCtx(in_pContext);
	USES_ERROR_HANDLING;


	DM_TRY
	{
		AlgorithmNavigationBase ^	pNavigator = GetNavigator(in_fForDMDimensionContent ? true : false);

		/////////////////////////////////////////////////////////////
		// Get the IDMCaseProcessor pointer from the managed object
		System::IntPtr wrappedUnk = System::Runtime::InteropServices::Marshal::GetIUnknownForObject( pNavigator );
		IUnknown*	pUnk = (IUnknown*)(INT_PTR)wrappedUnk;
		
		THROW_ON_FAILURE( pUnk->QueryInterface( __uuidof(IDMAlgorithmNavigation), (void**)out_ppDAGNav) );
	}
	DM_CATCHALL;

	REPORT_STATUS;
}


HRESULT AlgorithmBase::ExportedGetSampleCaseSet( 
    IDMContextServices *in_pContext,
    DMString *in_pstrNodeID,
    IDMPullCaseSet **out_ppCaseSet)
{
	AutoPushContext autoCtx(in_pContext);
	USES_ERROR_HANDLING;


	DM_TRY
	{
		// not implemented in the managed version
		// relatively easy to implement, but not used by most algorithms
		THROW_ON_FAILURE(E_NOTIMPL);
	}
	DM_CATCHALL;
	
	REPORT_STATUS;
}


HRESULT AlgorithmBase::ExportedGetTrainingParameterActualValue( 
    IDMContextServices *in_pContext,
    ULONG in_iParameter,
    DMVariantPtr io_pParameterValue)
{
	
	AutoPushContext autoCtx(in_pContext);
	USES_ERROR_HANDLING;
	
	VARIANT varParam;
	::VariantInit( &varParam );
	
	DM_TRY
	{
		CComPtr<IDMVariantPtrHandler> spVarHandler;
		THROW_ON_FAILURE( in_pContext->GetVariantHandler(&spVarHandler) );

		Object ^	value = GetTrainingParameterActualValue(in_iParameter);
		System::IntPtr wrapVariant = (System::IntPtr) (INT_PTR) &varParam;
		System::Runtime::InteropServices::Marshal::GetNativeVariantForObject( value, wrapVariant);
		
		THROW_ON_FAILURE( spVarHandler->CopyVariantToHandle( io_pParameterValue, &varParam ) );
	}
	DM_CATCHALL
	DM_FINALLY
	{
		::VariantClear( &varParam );
	}

	REPORT_STATUS;
}


HRESULT AlgorithmBase::ExportedHasFeatureSelection( 
    IDMContextServices *in_pContext,
    BOOL *out_pHasFS)
{
	AutoPushContext autoCtx(in_pContext);
	USES_ERROR_HANDLING;

	DM_TRY
	{
		bool	hasFS = this->HasFeatureSelection();
		*out_pHasFS	=	hasFS ? TRUE : FALSE;

	}
	DM_CATCHALL;
	
	REPORT_STATUS;
}


HRESULT AlgorithmBase::ExportedGetFeatureSelectedAttributes( 
    IDMContextServices *in_pContext,
    ULONG *out_pCount,
    DM_Attribute **out_prgFSAtt,
    DM_ATTRIBUTE_FLAGS **out_prgFSAttFlags)
{
	AutoPushContext autoCtx(in_pContext);
	USES_ERROR_HANDLING;

	CComPtr<IDMMemoryAllocator>    spVectorAlloc;
    *out_pCount =   0; 
    *out_prgFSAtt   =   NULL;
    *out_prgFSAttFlags = NULL;


	DM_TRY
	{
		array<System::UInt32>^		arAttributes = nullptr;
		array<AttributeFlags>^		arFlags		= nullptr;
		
		GetAllFeatureSelectionInfo(arAttributes, arFlags);

		if( arAttributes->Length != arFlags->Length )
		{
			throw gcnew System::InvalidOperationException(gcnew String(L"GetAllFeatureSelectionInfo"));
		}

        THROW_ON_FAILURE( Model->m_wpSvc->GetVectorAlloc( &spVectorAlloc ) );

		int cFSAtts = arFlags->Length ;
        
        BYTE*   pBuff = NULL;
        THROW_ON_FAILURE( spVectorAlloc->Alloc( (UINT) (cFSAtts*sizeof(DM_Attribute)), &pBuff) ); 
        *out_prgFSAtt = (DM_Attribute*)pBuff;

        THROW_ON_FAILURE( spVectorAlloc->Alloc( (UINT) (cFSAtts*sizeof(DM_ATTRIBUTE_FLAGS)), &pBuff) ); 
        *out_prgFSAttFlags = (DM_Attribute*)pBuff;


		for( int nIndex = 0; nIndex < cFSAtts; nIndex ++)
		{
			(*out_prgFSAtt)[nIndex] = arAttributes[nIndex];
			(*out_prgFSAttFlags)[nIndex] = (DM_ATTRIBUTE_FLAGS)arFlags[nIndex];
		}


	}
	DM_CATCH_EX(System::Exception^ ex)
	{
		DM_HANDLE_EXCEPTION(ex);

        if( spVectorAlloc )
        {
            if( *out_prgFSAtt )
                spVectorAlloc->Free( (BYTE*)*out_prgFSAtt );
            if( *out_prgFSAttFlags )
                spVectorAlloc->Free( (BYTE*)*out_prgFSAttFlags);
        }
        *out_pCount = 0;
        *out_prgFSAtt   =   NULL;
        *out_prgFSAttFlags  =   NULL;
	}
	
	REPORT_STATUS;
}


HRESULT AlgorithmBase::ExportedGetAttributeFeatureSelectionFlags( 
    IDMContextServices *in_pContext,
    DM_Attribute in_nAttr,
    DM_ATTRIBUTE_FLAGS *out_pAttFlags)
{
	AutoPushContext autoCtx(in_pContext);
	USES_ERROR_HANDLING;

	DM_TRY
	{
		AttributeFlags	flags = this->GetAttributeFeatureSelectionInfo( in_nAttr );
		*out_pAttFlags	=	(DM_ATTRIBUTE_FLAGS)flags;
	}
	DM_CATCHALL;
	REPORT_STATUS;
}


HRESULT AlgorithmBase::ExportedGetProcessingMemoryEstimate( 
    IDMContextServices *in_pContext,
    LONG *out_pEstimate)
{
	// Not implemented for managed plug-ins
	AutoPushContext autoCtx(in_pContext);
	USES_ERROR_HANDLING;

	DM_TRY
	{
		*out_pEstimate = -1;
	}
	DM_CATCHALL;
	
	REPORT_STATUS;
}


void AlgorithmBase::ExportedUpdateMemoryEstimate( 
    LONG in_lEstimateChange)
{
	// Not implemented for managed plug-ins
}


LONG AlgorithmBase::ExportedReservedMemoryEstimate( void)
{
	// Not implemented for managed plug-ins
	return 0;
}


HRESULT AlgorithmBase::ExportedRenderPMMLContent( 
    IDMContextServices *in_pContext,
    ISAXContentHandler *in_pXMLWriter)
{
	AutoPushContext autoCtx(in_pContext);
	USES_ERROR_HANDLING;

	DM_TRY
	{
		CComPtr<IMXAttributes> spMXAtt;
		THROW_ON_FAILURE( spMXAtt.CoCreateInstance(__uuidof(SAXAttributes60), NULL, CLSCTX_INPROC_SERVER) ) ;

		PMMLWriter^ xmlOnSAX  = gcnew PMMLWriter(in_pXMLWriter, spMXAtt); 

		// Render PMML into the XmlOnSAX XmlWriter
		RenderPMMLContent(xmlOnSAX);
	}
	DM_CATCHALL;
	
	REPORT_STATUS;
}


HRESULT AlgorithmBase::ExportedPreInitializeForPMML21Parsing( 
    IDMContextServices *in_pContext,
    IDMAttributeSet *in_pAttributeSet)
{
	AutoPushContext autoCtx(in_pContext);
	USES_ERROR_HANDLING;

	DM_TRY
	{
	}
	DM_CATCHALL;
	
	REPORT_STATUS;
}


HRESULT AlgorithmBase::ExportedGetPMMLAlgorithmSAXHandler( 
    IDMContextServices *in_pContext,
    ISAXContentHandler **out_ppSAXHandler)
{
	AutoPushContext autoCtx(in_pContext);
	USES_ERROR_HANDLING;

	DM_TRY
	{
		if( m_pmmlRecorder == nullptr )
		{
			m_pmmlStream	=	gcnew System::IO::MemoryStream();
			m_pmmlRecorder	=	gcnew System::Xml::XmlTextWriter( m_pmmlStream, System::Text::Encoding::Unicode);
		}
		PMMLSAXContentHandler*	pSAXHandler = NULL;
		THROW_ON_FAILURE( PMMLSAXContentHandler::CreateSAXHandler( in_pContext, this, &pSAXHandler) );
		
		pSAXHandler->SetInitialDepth(m_pmmlSwitchDepth);

		CComPtr<ISAXContentHandler> spSAX;
		CURRENT_STATUS	=	pSAXHandler->QueryInterface( __uuidof( ISAXContentHandler), (void**)out_ppSAXHandler);
	}
	DM_CATCHALL;
	
	REPORT_STATUS;
}



HRESULT AlgorithmBase::ExportedLoad(
	IDMContextServices* in_pContext,
	IDMPersistenceReader* in_pReader)
{
	AutoPushContext autoCtx(in_pContext);
	USES_ERROR_HANDLING;

	DM_TRY
	{
		// Initialize is called late, to ensure that the marginal statistics and the attribute set
		// are fully populated
		Initialize();

		PersistenceReader ^ pReader = gcnew PersistenceReader( in_pReader );
		LoadContent( pReader );
	}
	DM_CATCHALL;
	
	REPORT_STATUS;
}


HRESULT AlgorithmBase::ExportedSave(
	IDMContextServices* in_pContext,
	IDMPersistenceWriter* in_pWriter)
{
	AutoPushContext autoCtx(in_pContext);
	USES_ERROR_HANDLING;


	DM_TRY
	{

		// If the content was just loaded from PMML , then delegate PMML load to the plug-in
		// and then Save
		if( m_pmmlRecorder != nullptr )
		{
			m_pmmlRecorder->Flush();
			System::Xml::XmlDocument^ pmmlDoc = gcnew System::Xml::XmlDocument();

			m_pmmlStream->Position = 0;
			pmmlDoc->Load( m_pmmlStream );

			LoadPMMLContent( pmmlDoc->DocumentElement );
		}

		PersistenceWriter ^ pWriter = gcnew PersistenceWriter( in_pWriter );
		SaveContent( pWriter );
	}
	DM_CATCHALL;
	
	REPORT_STATUS;
}


HRESULT	AlgorithmBase::ExportedInterfaceSupportsErrorInfo(REFIID riid)
{
	static const IID* arr[] = 
	{
		&__uuidof(IDMAlgorithm),
		&__uuidof(IDMPersist),
		&__uuidof(IDMDispatch),
	};

	for (int i=0; i < sizeof(arr) / sizeof(arr[0]); i++)
	{
		if (InlineIsEqualGUID(*arr[i],riid))
			return S_OK;
	}
	return S_FALSE;
}


HRESULT AlgorithmBase::ExportedClusterMembership(
	IDMContextServices*			in_pContext,
	DM_CLUSTER_MEMBERSHIP_FLAGS	in_Flags,
	DMStringPtr					in_strClusterCaption,
	DM_CaseID					in_CaseID,
	ULONG						in_ulCaseValues,
	DM_ATTRIBUTE_VALUE *		in_rgValues,
	ULONG						in_ulMaxClusters,
	DM_CLUSTER_MEMBERSHIP **	io_prgClusters,
	ULONG *						out_pulClusters)
{
	AutoPushContext autoCtx(in_pContext);
	USES_ERROR_HANDLING;

	CComPtr<IDMStringHandler>	spStringHandler;
	RETURN_ON_FAILURE(in_pContext->GetStringHandler( &spStringHandler));
	
	CComPtr<IDMMemoryAllocator>	spVectorAlloc;
	RETURN_ON_FAILURE(in_pContext->GetVectorAlloc( &spVectorAlloc ));

	bool			fOwnsMemory = false;
	ULONG			cAllocSize  = 0;

	BSTR bstrTmp = NULL;


	DM_TRY
	{
		*io_prgClusters = NULL;
		*out_pulClusters = 0;

		// Extract parameters

		// strClusterCaption
		String ^		targetCluster  = String::Empty;
		if( in_strClusterCaption != NULL )
		{
			LPCWSTR	szTargetCluster	=	NULL;
			int		length	=	0;
			THROW_ON_FAILURE( spStringHandler->GetConstStringFromHandle( in_strClusterCaption, &szTargetCluster, (UINT*)&length) );
			targetCluster = gcnew String(szTargetCluster, 0, length);
		}
		
		// CaseID
		System::Int64	caseID = (in_CaseID == DM_UNSPECIFIED) ? -1 : (System::Int64)in_CaseID;

		// Case Values
		MiningCase ^	inputCase	=	gcnew MiningCase();
		DMCaseItr caseiter;
		
		// Initialize the case iterator with the input values
		caseiter.SetCase(in_rgValues, in_ulCaseValues);
		inputCase->SetCaseIterator( &caseiter );


		// Get the list of cluster membership
		array<ClusterMembershipInfo^>^	arClusters = ClusterMembership( caseID, inputCase, targetCluster);

		if( arClusters == nullptr )
			REPORT_STATUS;

		// Return only the first result if checking the membership for a specific cluster
		ULONG cClusters = (targetCluster->Length > 0 ) ? 1 : arClusters->Length;
		if( in_ulMaxClusters != DM_UNSPECIFIED )
		{
			if( cClusters > in_ulMaxClusters )
				cClusters  = in_ulMaxClusters;
		}

		// If caller didn't already allocate a structure, then allocate
		// one now big enough to hold just these clusters.
		if (NULL == *io_prgClusters)
		{
			byte*		outBuff = NULL;
			THROW_ON_FAILURE(spVectorAlloc->Alloc(cClusters*sizeof(DM_CLUSTER_MEMBERSHIP),  &outBuff) );
			*io_prgClusters =	(DM_CLUSTER_MEMBERSHIP*)outBuff;
			fOwnsMemory = true;
			cAllocSize = cClusters;
		}
		memset(*io_prgClusters, 0, sizeof(DM_CLUSTER_MEMBERSHIP) * cClusters);

		// Sort the results if necessary
		if( (arClusters->Length > 1) && (in_Flags & DMCMF_ORDER_NODES) )
		{
			// we'll use bubble sort since number of nodes is very small.
			for (ULONG i = 0;  i < (ULONG)arClusters->Length ;  i++)
			{
				for (ULONG j = i+1;  j < (ULONG)arClusters->Length ;  j++)
				{
					if (arClusters[i]->Membership < arClusters[j]->Membership )
					{
						ClusterMembershipInfo^ save = arClusters[i];
						arClusters[i] = arClusters[j];
						arClusters[j] = save;
					}
				}
			}
		}

		// Now copy the results
		for (ULONG i = 0;  i < cClusters;  i++)
		{
			DM_CLUSTER_MEMBERSHIP & rCluster = (*io_prgClusters)[i];

			rCluster.ulClusterID = (ULONG)arClusters[i]->ClusterId;
			rCluster.dblDistance = arClusters[i]->Distance;
			rCluster.dblMembership = arClusters[i]->Membership;

			if (in_Flags & DMCMF_INCLUDE_CAPTION && 
				arClusters[i]->Caption != nullptr && 
				arClusters[i]->Caption->Length > 0 )
			{
				THROW_ON_FAILURE( spStringHandler->CreateNewStringHandle(&rCluster.strCaption) );

				bstrTmp = (BSTR)(void*)System::Runtime::InteropServices::Marshal::StringToBSTR( arClusters[i]->Caption );
				THROW_ON_FAILURE( spStringHandler->CopyBufferToHandle( rCluster.strCaption, bstrTmp, arClusters[i]->Caption->Length) );

				System::Runtime::InteropServices::Marshal::FreeBSTR( (IntPtr)(void*)bstrTmp );
				bstrTmp = NULL;

			}

			if (in_Flags & DMCMF_INCLUDE_NODE_ID &&
				arClusters[i]->NodeUniqueName != nullptr && 
				arClusters[i]->NodeUniqueName->Length > 0 )

			{
				THROW_ON_FAILURE( spStringHandler->CreateNewStringHandle(&rCluster.strNodeID) );

				bstrTmp = (BSTR)(void*)System::Runtime::InteropServices::Marshal::StringToBSTR( arClusters[i]->NodeUniqueName );
				THROW_ON_FAILURE( spStringHandler->CopyBufferToHandle( rCluster.strNodeID, bstrTmp, arClusters[i]->NodeUniqueName->Length) );

				System::Runtime::InteropServices::Marshal::FreeBSTR( (IntPtr)(void*)bstrTmp );
				bstrTmp = NULL;
			}
		}


		if( NULL != out_pulClusters )
		{
			*out_pulClusters = cClusters;
		}

		arClusters = nullptr;
		REPORT_STATUS;
	}
	DM_CATCH_EX(System::Exception^ ex)
	{
		if( fOwnsMemory )
		{
			for(ULONG	nIndex = 0; nIndex < cAllocSize; nIndex ++ )
			{
				DM_CLUSTER_MEMBERSHIP & rCluster = (*io_prgClusters)[nIndex];
				if( rCluster.strCaption != NULL )
				{
					spStringHandler->DestroyStringHandle( rCluster.strCaption );
				}
				if( rCluster.strNodeID != NULL )
				{
					spStringHandler->DestroyStringHandle(rCluster.strNodeID);
				}
			}
			spVectorAlloc->Free((byte*)(*io_prgClusters));
			 *io_prgClusters = NULL;
		}
		*out_pulClusters = 0;
		
		DM_HANDLE_EXCEPTION(ex);
	}

	REPORT_STATUS;
}


HRESULT AlgorithmBase::ExportedCaseLikelihood(
	IDMContextServices*			in_pContext,
	DM_CASE_LIKELIHOOD_FLAGS	in_Flags,
	DM_CaseID					in_CaseID,
	ULONG						in_ulCaseValues,
	DM_ATTRIBUTE_VALUE *		in_rgValues,
	DOUBLE *					out_pdblCaseLikelihood)
{
	AutoPushContext autoCtx(in_pContext);
	USES_ERROR_HANDLING;

	DM_TRY
	{
		*out_pdblCaseLikelihood = 0.0;

		// Extract parameters

		// in_Flags
		System::Boolean normalizedLikelihood = (in_Flags & DMCLF_NONNORMALIZED) ? false: true;
		
		// CaseID
		System::Int64	caseID = (in_CaseID == DM_UNSPECIFIED) ? -1 : (System::Int64)in_CaseID;

		// Case Values
		MiningCase ^	inputCase	=	gcnew MiningCase();
		DMCaseItr caseiter;
		
		// Initialize the case iterator with the input values
		caseiter.SetCase(in_rgValues, in_ulCaseValues);
		inputCase->SetCaseIterator( &caseiter );


		// Get the list of cluster membership
		System::Double caseLikelihood = CaseLikelihood( caseID, inputCase, normalizedLikelihood);

		*out_pdblCaseLikelihood = caseLikelihood;

		REPORT_STATUS;
	}
	DM_CATCHALL;

	REPORT_STATUS;
}

